﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Security.Cryptography;
using SQLLib;
using System.ComponentModel;
using System.Security;
using System.Diagnostics;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using System.Data.SQLite;
using NetLib;


using FatSecretFoodEntriesResults = NetLib.FatSecretFoodEntriesResults;
using System.Collections.ObjectModel;


namespace MetaTracker.Model
{
    internal partial class TrackerModel : INotifyPropertyChanged
    {
        #region Data and Properties
        internal static TrackerModel SingletonInstance = null;

        #region User Info

        #region User Login Info

        private string _UserName = "";
        internal string UserName
        {
            get
            {
                return _UserName;
            }
            set
            {
                _UserName = value;
                NotifyPropertyChanged("UserName");
            }
        }

        private Users _currentUser = null;
        private Users CurrentUser
        {
            get
            {
                return _currentUser;
            }
            set
            {
                _currentUser = value;
                NotifyPropertyChanged("CurrentUser");
                NotifyPropertyChanged("HasWithings");
                NotifyPropertyChanged("HasFitbit");
                NotifyPropertyChanged("HasFatSecret");
                NotifyPropertyChanged("WithingsStatus");
                NotifyPropertyChanged("FitbitStatus");
                NotifyPropertyChanged("FatSecretStatus");
            }
        }

        private bool _isLoggedOn = false;
        internal bool IsLoggedOn
        {
            get
            {
                return _isLoggedOn;
            }
            private set
            {
                _isLoggedOn = value;
                NotifyPropertyChanged("IsLoggedOn");
                OnLoginStateChanged(EventArgs.Empty);

            }
        }

        private SecureString _password;
        internal SecureString Password
        {
            get
            {
                return _password;
            }
            set
            {
                _password = value;
            }
        }

        private string PasswordHash = "";

        internal string LoginMessage = "Log in: ";

        //used during registration of FatSecret
        private string FatSecretOAuthRequestToken;
        private string FatSecretOAuthRequestTokenSecret;
        private string FitbitOAuthRequestToken;
        private string FitbitOAuthRequestTokenSecret;

        #endregion //User Login Info

        #region Service Setup Related Data

        private bool WeightChangeMade = false;
        private bool ExerciseChangeMade = false;
        private bool FoodChangeMade = false;

        #region Selected Service Info

        internal ServiceEnum WeightService
        {
            get
            {
                if (CurrentUser == null
                    || CurrentUser.WeightService.Equals("none", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.None;
                else if (CurrentUser.WeightService.Equals("fitbit", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.Fitbit;
                else if (CurrentUser.WeightService.Equals("Withings", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.Withings;
                else if (CurrentUser.WeightService.Equals("FatSecret", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.FatSecret;
                else
                    throw new ArgumentException("Error: Invalid Weight Service");
            }
            set
            {
                if (value == ServiceEnum.FatSecret)
                    CurrentUser.WeightService = "FatSecret";
                else if (value == ServiceEnum.Fitbit)
                    CurrentUser.WeightService = "Fitbit";
                else if (value == ServiceEnum.Withings)
                    CurrentUser.WeightService = "Withings";
                else
                    CurrentUser.WeightService = "None";

                WeightChangeMade = true;
                UserHasWeight = (CurrentUser.WeightService == "None") ? false : true;
                NotifyPropertyChanged("WeightService");
                udc.SubmitChanges();
            }
        }
        internal ServiceEnum FoodIntakeService
        {
            get
            {
                if (CurrentUser == null
                    || CurrentUser.FoodIntakeService.Equals("none", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.None;
                else if (CurrentUser.FoodIntakeService.Equals("fitbit", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.Fitbit;
                else if (CurrentUser.FoodIntakeService.Equals("FatSecret", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.FatSecret;
                else
                    throw new ArgumentException("Error: Invalid Food Intake Service");
            }
            set
            {
                if (value == ServiceEnum.FatSecret)
                    CurrentUser.FoodIntakeService = "FatSecret";
                else if (value == ServiceEnum.Fitbit)
                    CurrentUser.FoodIntakeService = "Fitbit";
                else
                    CurrentUser.FoodIntakeService = "None";

                FoodChangeMade = true;
                UserHasFood = (CurrentUser.FoodIntakeService == "None") ? false : true;
                NotifyPropertyChanged("FoodIntakeService");
                udc.SubmitChanges();
            }
        }
        internal ServiceEnum ExerciseService
        {
            get
            {
                if (CurrentUser == null
                    || CurrentUser.ExerciseService.Equals("none", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.None;
                else if (CurrentUser.ExerciseService.Equals("fitbit", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.Fitbit;
                else if (CurrentUser.ExerciseService.Equals("FatSecret", StringComparison.CurrentCultureIgnoreCase))
                    return ServiceEnum.FatSecret;
                else
                    throw new ArgumentException("Error: Invalid Exercise Service");
            }
            set
            {
                if (value == ServiceEnum.FatSecret)
                    CurrentUser.ExerciseService = "FatSecret";
                else if (value == ServiceEnum.Fitbit)
                    CurrentUser.ExerciseService = "Fitbit";
                else
                    CurrentUser.ExerciseService = "None";

                ExerciseChangeMade = true;
                UserHasExercise = (CurrentUser.ExerciseService == "None") ? false : true;
                NotifyPropertyChanged("ExerciseService");
                udc.SubmitChanges();
            }
        }

        #endregion //Selected Service Info

        #region "User Has" Service or Service Type data

        internal bool HasWithings
        {
            get
            {
                if (CurrentUser == null || string.IsNullOrEmpty(CurrentUser.WithingsUserID))
                {
                    return false;
                }
                return true;
            }
        }
        internal bool HasFitbit
        {
            get
            {
                if (CurrentUser == null || string.IsNullOrEmpty(CurrentUser.FitbitOauthToken))
                {
                    return false;
                }
                return true;
            }
        }
        internal bool HasFatSecret
        {
            get
            {
                if (CurrentUser == null || string.IsNullOrEmpty(CurrentUser.FatSecretOauthToken))
                {
                    return false;
                }
                return true;
            }
        }

        private bool _UserHasWeight = false;
        internal bool UserHasWeight
        {
            get
            {
                return _UserHasWeight;
            }
            private set
            {
                _UserHasWeight = value;
                NotifyPropertyChanged("UserHasWeight");
                NotifyPropertyChanged("UserHasStatistics");
            }
        }
        private bool _UserHasExercise = false;
        internal bool UserHasExercise
        {
            get
            {
                return _UserHasExercise;
            }
            private set
            {
                _UserHasExercise = value;
                NotifyPropertyChanged("UserHasExercise");
                NotifyPropertyChanged("UserHasStatistics");
            }
        }
        private bool _UserHasFood = false;
        internal bool UserHasFood
        {
            get
            {
                return _UserHasFood;
            }
            private set
            {
                _UserHasFood = value;
                NotifyPropertyChanged("UserHasFood");
                NotifyPropertyChanged("UserHasStatistics");
            }
        }
        internal bool UserHasStatistics
        {
            get
            {
                return UserHasFood && UserHasExercise && UserHasWeight;
            }
        }

        #endregion //"User Has" Service or Service Type data

        

        #endregion //Service Setup Related Data

        #region Service Results Data

        

        //private List<FatSecretFoodEntriesResults> _FatSecretFoodResults = new List<FatSecretFoodEntriesResults>();
        //internal List<FatSecretFoodEntriesResults> FatSecretFoodResults
        //{
        //    get
        //    {
        //        return _FatSecretFoodResults;
        //    }
        //    private set
        //    {
        //        _FatSecretFoodResults = value;
        //        NotifyPropertyChanged("FatSecretFoodResults");
        //    }
        //}

        private ObservableCollection<IFoodIntakeResults> _FoodData = new ObservableCollection<IFoodIntakeResults>();
        internal ObservableCollection<IFoodIntakeResults> FoodData
        {
            get
            {
                return _FoodData;
            }
            set
            {
                _FoodData = value;
                NotifyPropertyChanged("FoodData");
            }
        }

        private ObservableCollection<IWeightResults> _WeightData = new ObservableCollection<IWeightResults>();
        internal ObservableCollection<IWeightResults> WeightData
        {
            get
            {
                return _WeightData;
            }
            set
            {
                _WeightData = value;
                NotifyPropertyChanged("WeightData");
            }
        }

        private ObservableCollection<IExerciseResults> _ExerciseData = new ObservableCollection<IExerciseResults>();
        internal ObservableCollection<IExerciseResults> ExerciseData
        {
            get
            {
                return _ExerciseData;
            }
            set
            {
                _ExerciseData = value;
                NotifyPropertyChanged("ExerciseData");
            }
        }

        private ObservableCollection<WeeklyStatistics> _WeeklyStats = new ObservableCollection<WeeklyStatistics>();
        public ObservableCollection<WeeklyStatistics> WeeklyStats
        {
            get
            {
                return _WeeklyStats;
            }
            private set
            {
                _WeeklyStats = value;
                NotifyPropertyChanged("WeeklyStats");
            }
        }

        #endregion //Service Results Data


        #endregion //User Info

        #region Database Info
        private const string UserDatabaseKey = "GcM3P2XPalE2Fpo6nsBG";

        //key = GcM3P2XPalE2Fpo6nsBG

        private readonly string _databaseFolder;
        internal string DatabaseFolder
        {
            get
            {
                return _databaseFolder;
            }
        }

        private readonly string _userDatabaseFile;
        internal string UserDatabaseFile
        {
            get
            {
                return _userDatabaseFile;
            }
        }

        private SQLiteExecutor Users;
        private SQLiteExecutor Data;

        private UsersDataContext udc;

        #endregion //Database Info

        #endregion //Data and Properties

        #region Constructors
        static TrackerModel()
        {
            //UserDatabaseKey = new SecureString();
            //string temp = "GcM3P2XPalE2Fpo6nsBG";
            //for( int i = 0; i < temp.Length; i++)
            //{
            //    UserDatabaseKey.AppendChar(temp[i]);
            //}
        }

        internal TrackerModel()
        {
            if (SingletonInstance == null)
            {
                SingletonInstance = this;
                //SHA1 fish = SHA1.Create();
            }
            else
            {
                throw new AccessViolationException(
                    "The TrackerModel class is a singleton, and a second instances cannot be instantiated");
            }

            _databaseFolder = ConfigurationManager.AppSettings["Database Folder"];
            if( !string.IsNullOrEmpty(_databaseFolder) && _databaseFolder.Last() != '\\' )
            {
                _databaseFolder += '\\';
            }
            _userDatabaseFile = ConfigurationManager.AppSettings["User Database Name"];

            SetupUsersDatabase();

        }
        ~TrackerModel()
        {
            SingletonInstance = null;
        }
        #endregion //constructors

        #region Setup Methods

        private bool SetupUsersDatabase()
        {
            //check to see if file exists, and make if not
            bool createTable = false;
            if (!System.IO.File.Exists(_databaseFolder + _userDatabaseFile))
            {
                createTable = true;
            }


            bool error = false;
            Users = new SQLiteExecutor();
            string results = Users.ExecuteCommand(
                string.Format("!CONNECT {0}{1}:{2}", DatabaseFolder, UserDatabaseFile, UserDatabaseKey), out error);

            if (error == true)
            {
                return error;
            }
            else
            {
                if (createTable)
                {
                    StringBuilder createTableQuery = new StringBuilder();
                    createTableQuery.AppendLine(" CREATE TABLE Users");
                    createTableQuery.AppendLine(" (");
                    createTableQuery.AppendLine("  UserID                INTEGER         PRIMARY KEY AUTOINCREMENT,");
                    createTableQuery.AppendLine("  UserName              varchar(50)     UNIQUE,");
                    createTableQuery.AppendLine("  PasswordHash          char(20)        NOT NULL,");
                    createTableQuery.AppendLine("  WithingsUserID        varchar(50)     ,");
                    createTableQuery.AppendLine("  WithingsPublicKey     varchar(50)     ,");
                    createTableQuery.AppendLine("  FatSecretOauthToken   varchar(50)     ,");
                    createTableQuery.AppendLine("  FatSecretOauthSecret  varchar(50)     ,");
                    createTableQuery.AppendLine("  FitbitOauthToken      varchar(50)     ,");
                    createTableQuery.AppendLine("  FitibtOauthSecret     varchar(50)     ,");
                    createTableQuery.AppendLine("  WeightService         varchar(50)     DEFAULT( 'None' ),");
                    createTableQuery.AppendLine("  FoodIntakeService     varchar(50)     DEFAULT( 'None' ),");
                    createTableQuery.AppendLine("  ExerciseService       varchar(50)     DEFAULT( 'None' )");
                    createTableQuery.AppendLine(" );");
                    Users.ExecuteCommand(createTableQuery.ToString(), out error);
                }


                Users.ExecuteCommand("!close", out error);

                // +
                //"DbLinqConnectionType=System.Data.SQLite.SQLiteConnection, System.Data.SQLite, Version=1.0.61.0, Culture=neutral, PublicKeyToken=db937bc2d44ff139" +
                //"Data Source=Filename.db3"
                //string connection = "DbLinqProvider=Sqlite; " //DbLinqConnectionType=System.Data.SQLite.SQLiteConnection, System.Data.SQLite
                //    + string.Format("Data Source={0}{1};Password={2};", DatabaseFolder, UserDatabaseFile, UserDatabaseKey); 
                var connection = new SQLiteConnection("DbLinqProvider=Sqlite; " //DbLinqConnectionType=System.Data.SQLite.SQLiteConnection, System.Data.SQLite
                     + string.Format("Data Source={0}{1};Password={2};", DatabaseFolder, UserDatabaseFile, UserDatabaseKey));

                udc = new UsersDataContext(connection);




                return true;
            }
        }

        #endregion //Setup Methods

        #region Register Services Methods

        [Obsolete("Apparently, this no longer works due to API changes. Register with Public Key instead.")]
        internal bool RegisterWithings(string withingsUser, SecureString withingsPassword)
        {
            //WARNING: Password is in plaintext in memory with this method!

            IntPtr bstr = Marshal.SecureStringToBSTR(withingsPassword);
            string plainTxt;

            try
            {
                plainTxt = Marshal.PtrToStringBSTR(bstr);
            }
            finally
            {
                Marshal.FreeBSTR(bstr);
            }
            
            var results = WithingsFetcher.FetchWithingsAccountUserList(withingsUser, plainTxt);
            
            switch (results.Count)
            {
                case 0:
                    //ERROR!
                    return false;
                case 1:
                    //only one, so register this one
                    CurrentUser.WithingsPublicKey = results[0].publickey;
                    CurrentUser.WithingsUserID = results[0].id.ToString();
                    udc.SubmitChanges();
                    NotifyPropertyChanged("HasWithings");
                    return true;
                default:
                    //Multiple sub-accounts.  let user pick one
                    //TODO: let user pick between multiple accounts
                    throw new NotImplementedException("MultipleUsers are not supported");
            }
        }

        internal bool? RegisterWithingsWithPublicKey(string withingsUserID, string withingsPublicKey)
        {
            bool? results = WithingsFetcher.ConfirmWithingsUserExists(withingsUserID, withingsPublicKey);

            if (results == null || results == false)
            {
                return results;
            }
            else //true
            {
                CurrentUser.WithingsPublicKey = withingsPublicKey;
                CurrentUser.WithingsUserID = withingsUserID;
                udc.SubmitChanges();

                NotifyPropertyChanged("HasWithings");
                NotifyPropertyChanged("UserHasWeight");
                NotifyPropertyChanged("UserHasStatistics");




                return results;
            }

        }

        internal string RegisterFatSecret_GetRequetURL()
        {
            var results = FatSecretFetcher.NewUserRequestToken();
            FatSecretOAuthRequestToken = results[1];
            FatSecretOAuthRequestTokenSecret = results[2];
            return results[0];
        }

        internal bool RegisterFatSecret_GetAccessToken(string validation)
        {
            Contract.Requires(FatSecretOAuthRequestToken != null);
            Contract.Requires(FatSecretOAuthRequestTokenSecret != null);

            if (FatSecretOAuthRequestToken == null || FatSecretOAuthRequestTokenSecret == null)
            {
                return false;
            }

            var results = FatSecretFetcher.NewUserAccessToken(FatSecretOAuthRequestToken,
                FatSecretOAuthRequestTokenSecret, validation);

            if (results.Count() == 0)
            {
                return false;
            }


            var split = results.Split('&');
            CurrentUser.FatSecretOauthToken = split[0].Split('=')[1];
            CurrentUser.FatSecretOauthSecret = split[1].Split('=')[1];

            udc.SubmitChanges();

            NotifyPropertyChanged("HasFatSecret");

            return true;
        }


        internal string RegisterFitbit_GetRequetURL()
        {
            var results =  FitbitFetcher.GetRequestCredentials();

            
            FitbitOAuthRequestToken = results["oauth_token"];
            FitbitOAuthRequestTokenSecret = results["oauth_token_secret"];
            return results["OAuthRequestURL"];
        }

        internal bool RegisterFitbit_GetAccessToken(string validation)
        {
            Contract.Requires(FitbitOAuthRequestToken != null);
            Contract.Requires(FitbitOAuthRequestTokenSecret != null);

            if (FitbitOAuthRequestToken == null || FitbitOAuthRequestTokenSecret == null)
            {
                return false;
            }

            
            var results = 
                FitbitFetcher.GetAccessTokens(FitbitOAuthRequestToken, FitbitOAuthRequestTokenSecret, validation);
            
            if (results.Count() == 0)
            {
                return false;
            }
            
            CurrentUser.FitbitOauthToken = results[0].Split('=')[1];
            CurrentUser.FitibtOauthSecret = results[1].Split('=')[1];

            udc.SubmitChanges();

            NotifyPropertyChanged("HasFitbit");

            return true;
        }

        #endregion //Register Services Methods

        #region INotifyMethods
        public event PropertyChangedEventHandler PropertyChanged;
        protected void NotifyPropertyChanged(String info)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(info));
            }
        }


        public event UpdateBindingsEventHandler LoginStateChanged;
        public delegate void UpdateBindingsEventHandler(object sender, EventArgs e);
        protected virtual void OnLoginStateChanged(EventArgs e)
        {
            if (LoginStateChanged != null)
                LoginStateChanged(this, e);
        }
        #endregion //INotifyMethods

        #region User Manipulation Methods
        internal void Login()
        {
            WeightChangeMade = true;
            ExerciseChangeMade = true;
            FoodChangeMade = true;

            var user = udc.Users.SingleOrDefault(x => x.UserName == _UserName);

            if (user == null)
            {
                IsLoggedOn = false;
                LoginMessage = "User Name or Password is incorrect";
                CurrentUser = null;
                UserHasWeight = false;
                UserHasFood = false;
                UserHasExercise = false;

            }
            else
            {
                PasswordHash = SecureStringToSHA1(Password);

                if (user.PasswordHash != PasswordHash)
                {
                    IsLoggedOn = false;
                    LoginMessage = "User Name or Password is incorrect";
                    CurrentUser = null;
                }
                else
                {
                    IsLoggedOn = true;
                    LoginMessage = "Success!\n\nGo to the \"User Settings\" tab to set up your services";
                    CurrentUser = user;

                    NotifyPropertyChanged("IsLoggedOn");
                    NotifyPropertyChanged("HasWithings");
                    NotifyPropertyChanged("HasFitbit");
                    NotifyPropertyChanged("HasFatSecret");
                    NotifyPropertyChanged("WeightService");
                    NotifyPropertyChanged("FoodIntakeService");
                    NotifyPropertyChanged("ExerciseService");

                    UserHasWeight = (CurrentUser.WeightService == "None") ? false : true;
                    UserHasExercise = (CurrentUser.ExerciseService == "None") ? false : true;
                    UserHasFood = (CurrentUser.FoodIntakeService == "None") ? false : true;


                    //TODO: Open up the user's database file
                }
            }
        }
        internal void Logoff()
        {
            WeightChangeMade = true;
            ExerciseChangeMade = true;
            FoodChangeMade = true;

            UserHasExercise = false;
            UserHasFood = false;
            UserHasWeight = false;

            IsLoggedOn = false;
            CurrentUser = null;
            Password.Clear();

            LoginMessage = "Login: ";
            UserName = "";


            //clear statistics
            ExerciseData.Clear();
            FoodData.Clear();
            WeightData.Clear();



            NotifyPropertyChanged("IsLoggedOn");
            NotifyPropertyChanged("HasWithings");
            NotifyPropertyChanged("HasFitbit");
            NotifyPropertyChanged("HasFatSecret");
            NotifyPropertyChanged("WeightService");
            NotifyPropertyChanged("FoodIntakeService");
            NotifyPropertyChanged("ExerciseService");

        }

        internal string RegisterNewUser(string NewUserName, SecureString one, out bool success)
        {
            try
            {
                udc.Users.Single(x => x.UserName == NewUserName);

                success = false;
                return "That User Name is already taken";
            }
            catch (Exception)
            {
                Users newUser = new Users()
                {
                    UserName = NewUserName,
                    PasswordHash = SecureStringToSHA1(one),
                    ExerciseService = "None",
                    FoodIntakeService = "None",
                    WeightService = "None"
                };
                udc.Users.InsertOnSubmit(newUser);
                udc.SubmitChanges();

                success = true;
                return "Registered!";
            }

        }

        internal bool DeleteService(ServiceEnum service)
        {
            WeightChangeMade = true;

            if (service == ServiceEnum.Withings)
            {
                if (WeightService == ServiceEnum.Withings)
                {
                    WeightService = ServiceEnum.None;
                }
                CurrentUser.WithingsUserID = "";
                CurrentUser.WithingsPublicKey = "";
                udc.SubmitChanges();
            }
            else if (service == ServiceEnum.FatSecret)
            {
                CurrentUser.FatSecretOauthSecret = "";
                CurrentUser.FatSecretOauthToken = "";
                udc.SubmitChanges();
            }
            else if (service == ServiceEnum.Fitbit)
            {
                CurrentUser.FitbitOauthToken = "";
                CurrentUser.FitibtOauthSecret = "";
                udc.SubmitChanges();
            }
            else
            {
                return false; //Given bad service
            }

            NotifyPropertyChanged("HasWithings");
            NotifyPropertyChanged("HasFitbit");
            NotifyPropertyChanged("HasFatSecret");
            NotifyPropertyChanged("WeightService");
            NotifyPropertyChanged("FoodIntakeService");
            NotifyPropertyChanged("ExerciseService");
            return true;
        }

        #endregion User Manipulation Methods




        
    }
}
